package com.youboy.oauth.provider;

import com.youboy.oauth.common.OAuthParameters;
import com.youboy.oauth.common.Precondition;
import com.youboy.oauth.consumer.InvalidConsumerKeyException;
import com.youboy.oauth.consumer.OAuthConsumerService;
import com.youboy.oauth.model.OAuthConsumer;
import com.youboy.oauth.model.OAuthConsumerAuthentication;
import com.youboy.oauth.model.OAuthConsumerCredentials;
import com.youboy.oauth.model.OAuthRequest;
import com.youboy.oauth.nonce.InvalidNonceException;
import com.youboy.oauth.nonce.OAuthNonceService;
import com.youboy.oauth.signature.InvalidSignatureException;
import com.youboy.oauth.signature.OAuthSignatureFactory;
import com.youboy.oauth.signature.OAuthSignatureMethod;
import com.youboy.oauth.signature.UnsupportedSignatureMethodException;
import com.youboy.oauth.token.InvalidTokenException;
import com.youboy.oauth.token.OAuthToken;
import com.youboy.oauth.token.OAuthTokenService;

/**
 * 
 * @author loudyn
 * 
 */
public abstract class OAuthProviderSupport implements OAuthProvider {

	public class EmptyOAuthToken implements OAuthToken {

		private final String consumerKey;

		public EmptyOAuthToken(String consumerKey) {
			this.consumerKey = consumerKey;
		}

		@Override
		public String getValue() {
			return "";
		}

		@Override
		public String getCallbackUrl() {
			return "";
		}

		@Override
		public String getVerifier() {
			return "";
		}

		@Override
		public String getSecret() {
			return "";
		}

		@Override
		public String getConsumerKey() {
			return this.consumerKey;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see com.youboy.oauth.token.OAuthToken#isAccessToken()
		 */
		@Override
		public boolean isAccessToken() {
			// must return false!
			return false;
		}

		@Override
		public void validateExpired() throws InvalidTokenException {
		}

		@Override
		public void validateVerifier(String verifier) throws InvalidTokenException {
		}

	}

	private final OAuthRequest oauthRequest;
	private final OAuthConsumerService oauthConsumerService;
	private final OAuthNonceService oauthNonceService;
	private final OAuthTokenService oauthTokenService;
	private final OAuthSignatureFactory oauthSignatureFactory;

	protected final static String OAUTH_CONSUMER_ATTRIBUTE = "oauth.consumer.attribute#request";
	protected final static String OAUTH_TOKEN_ATTRIBUTE = "oauth.token.attribute#request";
	protected final static String OAUTH_CONSUMER_CREDENTIALS_ATTRIBUTE = "oauth.consumer.credentials.attribute#request";

	/**
	 * 
	 * @param oauthRequest
	 * @param oauthConsumerService
	 * @param oauthTokenService
	 * @param oauthNonceService
	 * @param oauthSignatureFactory
	 */
	public OAuthProviderSupport(final OAuthRequest oauthRequest, final OAuthConsumerService oauthConsumerService,
								final OAuthTokenService oauthTokenService, final OAuthNonceService oauthNonceService,
								final OAuthSignatureFactory oauthSignatureFactory) {

		this.oauthRequest = oauthRequest;
		this.oauthConsumerService = oauthConsumerService;
		this.oauthNonceService = oauthNonceService;
		this.oauthTokenService = oauthTokenService;
		this.oauthSignatureFactory = oauthSignatureFactory;
	}

	/**
	 * 
	 * @return
	 */
	protected final OAuthRequest getOAuthRequest() {
		return oauthRequest;
	}

	/**
	 * 
	 * @return
	 */
	protected final OAuthConsumerService getOAuthConsumerService() {
		return oauthConsumerService;
	}

	/**
	 * 
	 * @return
	 */
	protected final OAuthNonceService getOAuthNonceService() {
		return oauthNonceService;
	}

	/**
	 * 
	 * @return
	 */
	protected final OAuthTokenService getOAuthTokenService() {
		return oauthTokenService;
	}

	/**
	 * 
	 * @return
	 */
	protected final OAuthSignatureFactory getOAuthSignatureFactory() {
		return oauthSignatureFactory;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.youboy.oauth.provider.OAuthProvider#exceptOAuthConsumer()
	 */
	@Override
	public final OAuthProvider exceptOAuthConsumer() throws InvalidConsumerKeyException {
		String consumerKey = getOAuthRequestParameter(OAuthParameters.OAUTH_CONSUMER_KEY.asString());
		OAuthConsumer consumer = getOAuthConsumerService().loadConsumerByConsumerKey(consumerKey);

		if (null == consumer) {
			throw new InvalidConsumerKeyException(String.format("Invalid consumer key(%s)", consumerKey));
		}

		getOAuthRequest().setAttribute(OAUTH_CONSUMER_ATTRIBUTE, consumer);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.youboy.oauth.provider.OAuthProvider#exceptOAuthToken()
	 */
	@Override
	public OAuthProvider exceptOAuthToken() throws InvalidTokenException {

		String oauthTokenString = getOAuthRequestParameter(OAuthParameters.OAUTH_TOKEN.asString());
		OAuthToken oauthToken = getOAuthTokenService().getOAuthToken(oauthTokenString);

		// when consumer request for a unauthorized oauthToken,create a empty OAuthToken.
		if (null == oauthToken) {
			String consumerKey = getOAuthRequestParameter(OAuthParameters.OAUTH_CONSUMER_KEY.asString());
			oauthToken = new EmptyOAuthToken(consumerKey);
		}

		oauthToken.validateExpired();
		getOAuthRequest().setAttribute(OAUTH_TOKEN_ATTRIBUTE, oauthToken);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.youboy.oauth.provider.OAuthProvider#exceptOAuthNonce()
	 */
	@Override
	public final OAuthProvider exceptOAuthNonce() throws InvalidNonceException {

		String consumerKey = getOAuthRequestParameter(OAuthParameters.OAUTH_CONSUMER_KEY.asString());
		String oauthTimestamp = getOAuthRequestParameter(OAuthParameters.OAUTH_TIMESTAMP.asString());
		String oauthNonce = getOAuthRequestParameter(OAuthParameters.OAUTH_NONCE.asString());

		long oauthTimestampAsLong = 0;

		try {

			oauthTimestampAsLong = Long.parseLong(oauthTimestamp);
		} catch (Exception e) {
			throw new InvalidNonceException("Invalid oauthTimestamp!");
		}

		oauthNonceService.validateOAuthNonce(consumerKey, oauthTimestampAsLong, oauthNonce);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.youboy.oauth.provider.OAuthProvider#exceptOAuthSignature()
	 */
	@Override
	public final OAuthProvider exceptOAuthSignature() throws UnsupportedSignatureMethodException, InvalidSignatureException {

		OAuthConsumer consumer = (OAuthConsumer) getOAuthRequestAttribute(OAUTH_CONSUMER_ATTRIBUTE);
		OAuthToken oauthToken = (OAuthToken) getOAuthRequestAttribute(OAUTH_TOKEN_ATTRIBUTE);

		Precondition.notNull(consumer, "must call exceptOAuthConsumer before exceptOAuthSignature!");
		Precondition.notNull(oauthToken, "must call exceptOAuthToken before exceptOAuthSignature!");

		String signature = getOAuthRequestParameter(OAuthParameters.OAUTH_SIGNATURE.asString());
		String signatureMethodString = getOAuthRequestParameter(OAuthParameters.OAUTH_SIGNATURE_METHOD.asString());
		String signatureBaseString = getOAuthRequest().getSignatureBaseString();

		String signatureSecret = new StringBuilder().append(consumer.getConsumerSecret())
													.append("&")
													.append(oauthToken.getSecret())
													.toString();

		OAuthSignatureMethod signatureMethod = getOAuthSignatureFactory().getSignatureMethod(
																							signatureMethodString, signatureSecret
																						);
		signatureMethod.verify(signature, signatureBaseString);

		// when verify the signature,create a consumerCredentials.
		OAuthConsumerCredentials credentials = new OAuthConsumerCredentials(
																			consumer.getConsumerKey(),
																			signature,
																			signatureMethodString,
																			signatureBaseString,
																			oauthToken.getValue()
																		);

		getOAuthRequest().setAttribute(OAUTH_CONSUMER_CREDENTIALS_ATTRIBUTE, credentials);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.youboy.oauth.provider.OAuthProvider#returnOAuthConsumerAuthentication()
	 */
	@Override
	public OAuthConsumerAuthentication returnOAuthConsumerAuthentication() {
		OAuthConsumer consumer = (OAuthConsumer) getOAuthRequestAttribute(OAUTH_CONSUMER_ATTRIBUTE);
		OAuthConsumerCredentials credentials = (OAuthConsumerCredentials) getOAuthRequestAttribute(OAUTH_CONSUMER_CREDENTIALS_ATTRIBUTE);
		return new OAuthConsumerAuthentication(consumer, credentials, getOAuthRequest().getOAuthParameters());
	}

	protected final String getOAuthRequestParameter(String parameterName) {
		return getOAuthRequest().getOAuthParameters().get(parameterName);
	}

	protected final Object getOAuthRequestAttribute(String attrName) {
		return getOAuthRequest().getAttribute(attrName);
	}
}
